﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using OpenTK;

namespace Painter3D.RenderSys
{
    /// <summary>
    /// 从着色器文本中获得的必要信息
    /// </summary>
    public class ShaderInfo
    {
        /// <summary>
        /// 顶点数据请求标志
        /// 修改时随同调整<seealso cref="VertexRequestTagEnumExtension"/>
        /// </summary>
        public enum VertexRequestTagEnum
        {
            /// <summary>
            /// 顶点坐标
            /// </summary>
            Position = 0,
            ///顶点坐标 二维
            Position2,
            /// <summary>
            /// 顶点色
            /// </summary>
            Color,
            /// <summary>
            /// 顶点法向量
            /// </summary>
            Normal,
            /// <summary>
            /// UV布局1
            /// </summary>
            UV1,
            /// <summary>
            /// UV布局2
            /// </summary>
            UV2,
            /// <summary>
            /// 永远放在最后，但不要使用
            /// </summary>
            END
        };
        public struct VertexRequestInfo
        {
            public VertexRequestTagEnum tag;
            public int point;

            public VertexRequestInfo(VertexRequestTagEnum tag, int point)
            {
                this.tag = tag;
                this.point = point;
            }
        }
        public struct UniformInfo
        {
            /// <summary>
            /// 变量名称
            /// </summary>
            public string name;
            /// <summary>
            /// <para>索引值</para>
            /// <para>对于图像纹理，记录绑定的纹理单元</para>
            /// <para>对于数值型变量，记录更新时使用的变量位置</para>
            /// <para>对于变量块中的变量，记录更新时该变量的偏移值</para>
            /// </summary>
            public int index;
            /// <summary>
            /// 变量的类型 包含数值型和图像纹理
            /// </summary>
            public Type type;

            public UniformInfo(string name, int index, Type type)
            {
                this.name = name ?? throw new ArgumentNullException(nameof(name));
                this.index = index;
                this.type = type ?? throw new ArgumentNullException(nameof(type));
            }
        }
        /// <summary>
        /// 请使用构造方法创建，从而自动计算布局对齐
        /// </summary>
        public struct UniformBlockInfo
        {
            readonly public string blockName;
            public int bindingPoint;
            readonly public int byteSizeInSTD140;
            public UniformInfo[] uniformInfos
            {
                get; private set;
            }
            /// <summary>
            /// 构造一个uniformBlockInfod,该对象只有通过本方法构建才是有意义的
            /// </summary>
            /// <param name="blockName">块名称</param>
            /// <param name="uniformInfos">内部变量信息</param>
            public UniformBlockInfo(string blockName, UniformInfo[] uniformInfos)
            {
                this.blockName = blockName ?? throw new ArgumentNullException(nameof(blockName));
                this.bindingPoint = 0;
                this.byteSizeInSTD140 = 0;
                this.uniformInfos = uniformInfos ?? throw new ArgumentNullException(nameof(uniformInfos));
                //为uniform计算其布局信息
                int byteSize = 0;
                for (int i = 0; i < uniformInfos.Length; i++)
                {
                    uniformInfos[i].index = byteSize;
                    int size = Marshal.SizeOf(uniformInfos[i].type);
                    byteSize += (size / 16 + 1) * 16;
                }
                this.byteSizeInSTD140 = byteSize;
                //设置绑定点
                this.bindingPoint = UniformBlock.GetBlockBinding(this);
            }
        }
        //对顶点数据的要求
        readonly public VertexRequestInfo[] vertexRequestInfos;
        //对变量以及图像纹理的要求
        readonly public UniformInfo[] uniformInfos;
        //对变量块的要求
        readonly public UniformBlockInfo[] blockInfos;

        /// <summary>
        /// 解析着色器的信息
        /// </summary>
        /// <param name="vertShaderSource">顶点着色器源码</param>
        /// <param name="fragShaderSource">片段着色器源码</param>
        /// <param name="self">着色器自身对象</param>
        public ShaderInfo(string vertShaderSource, string fragShaderSource, Shader self)
        {
            //解析顶点数据请求
            List<VertexRequestInfo> vertexInfoList = new List<VertexRequestInfo>();
            for (int i = 0; i < (int)VertexRequestTagEnum.END; i++)
            {
                VertexRequestTagEnum MatchRequest = (VertexRequestTagEnum)i;
                Match match = Regex.Match(vertShaderSource, @"layout\(location=(\d)\)in " + MatchRequest.TagTypeString() + " " + MatchRequest.ToString() + ";");
                if (match.Success)
                {
                    int binding = int.Parse(match.Groups[1].Value);
                    vertexInfoList.Add(new VertexRequestInfo((VertexRequestTagEnum)i, binding));
                }
            }

            vertexRequestInfos = vertexInfoList.ToArray();
            //解析变量与材质请求
            List<UniformInfo> uniformInfoList = new List<UniformInfo>();
            MatchCollection matches = Regex.Matches(vertShaderSource + fragShaderSource, @"uniform (\w+) (\w+);");
            int TextureUnitCounter = 0;
            for (int i = 0; i < matches.Count; i++)
            {
                Type type = TypeConvertGLSL2Csharp(matches[i].Groups[1].Value);
                if (type != null)
                {
                    string name = matches[i].Groups[2].Value;
                    if (type == typeof(Texture2D) || type == typeof(TextureBox))
                    {
                        TextureUnitCounter++;
                        uniformInfoList.Add(new UniformInfo(name, TextureUnitCounter, type));
                    }
                    else
                    {
                        uniformInfoList.Add(new UniformInfo(name, self.GetUniformIndex(name), type));
                    }
                }
            }
            uniformInfos = uniformInfoList.ToArray();
            //解析变量块请求
            List<UniformBlockInfo> uniformBlockInfoList = new List<UniformBlockInfo>();
            MatchCollection matchBlocks = Regex.Matches(
                vertShaderSource,
                @"layout\(std140\) uniform ([A-Za-z0-9]+)\s*\n*{([\s\S]+?)\n*\s*};");
            for (int i = 0; i < matchBlocks.Count; i++)
            {
                Match matchBlock = matchBlocks[i];
                string blockName = matchBlock.Groups[1].Value;
                MatchCollection matchUniforms = Regex.Matches(matchBlock.Groups[2].Value, @"\s*([A-Za-z0-9]+)\s+([A-Za-z0-9]+);\n*\s*");
                int count = matchUniforms.Count;
                UniformInfo[] infos = new UniformInfo[count];
                for (int k = 0; k < count; k++)
                {
                    infos[k].name = matchUniforms[k].Groups[2].Value;
                    infos[k].type = TypeConvertGLSL2Csharp(matchUniforms[k].Groups[1].Value);
                }
                uniformBlockInfoList.Add(new UniformBlockInfo(blockName, infos));
            }
            blockInfos = uniformBlockInfoList.ToArray();
        }

        private Type TypeConvertGLSL2Csharp(string type)
        {
            switch (type)
            {
                case "mat4":
                    return typeof(Matrix4);
                case "vec4":
                    return typeof(Vector4);
                case "vec3":
                    return typeof(Vector3);
                case "vec2":
                    return typeof(Vector2);
                case "sampler2D":
                    return typeof(Texture2D);
                case "samplerCube":
                    return typeof(TextureBox);
                default:
                    Log.LogOut("着色器类型转换错误", "shader Type error" + type);
                    break;
            }
            return typeof(object);
        }
        /// <summary>
        /// 从着色器信息中获取变量的索引
        /// </summary>
        /// <param name="name">变量名称</param>
        /// <param name="type">该变量的类型 null时不进行类型检查</param>
        /// <returns>变量索引，-1为查询失败</returns>
        public int GetUniformIndexByInfo(string name, Type type = null)
        {
            foreach (var uInfo in uniformInfos)
            {
                if (uInfo.name == name && (uInfo.type == type || type == null))
                {
                    if (uInfo.type != type && type != null)
                    {
                        Log.LogOut("shaderinfo 查询时出错,变量类型异常", "shaderinfo type error" + name);
                    }
                    return uInfo.index;
                }
            }
            return -1;
        }
    }

    public static class VertexRequestTagEnumExtension
    {
        /// <summary>
        /// 给出该请求需要的数据字节长度
        /// </summary>
        /// <param name="self">VertexRequestTagEnum</param>
        /// <returns>数据字节长度</returns>
        public static int TagSize(this ShaderInfo.VertexRequestTagEnum self)
        {
            switch (self)
            {
                case ShaderInfo.VertexRequestTagEnum.Position:
                    return 3 * sizeof(float);
                case ShaderInfo.VertexRequestTagEnum.Color:
                    return 3 * sizeof(float);
                case ShaderInfo.VertexRequestTagEnum.Normal:
                    return 3 * sizeof(float);
                case ShaderInfo.VertexRequestTagEnum.UV1:
                    return 2 * sizeof(float);
                case ShaderInfo.VertexRequestTagEnum.UV2:
                    return 2 * sizeof(float);
                case ShaderInfo.VertexRequestTagEnum.Position2:
                    return 2 * sizeof(float);
                case ShaderInfo.VertexRequestTagEnum.END:
                default:
                    throw new Exception("该枚举数据长度未定义" + self.ToString());
            }
        }

        /// <summary>
        /// 给出该请求需要的浮点个数
        /// </summary>
        /// <param name="self">VertexRequestTagEnum</param>
        /// <returns>数据浮点个数</returns>
        public static int TagCount(this ShaderInfo.VertexRequestTagEnum self)
        {
            switch (self)
            {
                case ShaderInfo.VertexRequestTagEnum.UV1:
                case ShaderInfo.VertexRequestTagEnum.UV2:
                case ShaderInfo.VertexRequestTagEnum.Position2:
                    return 2;
                case ShaderInfo.VertexRequestTagEnum.Position:
                case ShaderInfo.VertexRequestTagEnum.Normal:
                case ShaderInfo.VertexRequestTagEnum.Color:
                    return 3;
                case ShaderInfo.VertexRequestTagEnum.END:
                default:
                    throw new Exception("该枚举数据长度未定义" + self.ToString());
            }
        }

        public static string TagTypeString(this ShaderInfo.VertexRequestTagEnum self)
        {
            switch (self)
            {
                case ShaderInfo.VertexRequestTagEnum.UV1:
                case ShaderInfo.VertexRequestTagEnum.UV2:
                case ShaderInfo.VertexRequestTagEnum.Position2:
                    return "vec2";
                case ShaderInfo.VertexRequestTagEnum.Position:
                case ShaderInfo.VertexRequestTagEnum.Normal:
                case ShaderInfo.VertexRequestTagEnum.Color:
                    return "vec3";
                case ShaderInfo.VertexRequestTagEnum.END:
                default:
                    throw new Exception("未定义该顶点数据请求的类型字符串");
            }
        }
    }
}
